using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEngine;
using WFTools;

/// <summary>
/// XML表格读取器，可能在读取超大行文件时会卡顿
/// 需要将XML读取形式换成流式读取 XmlReader 
/// </summary>
public class XMLLoader : Editor
{
    static Dictionary<string, SheetData> sheetDatas;
    static Dictionary<string, TypeSheetData> typeSheetDatas;
    static Dictionary<string, EnumSheetData> enumSheetDatas;
    //[ContextMenu("Start")]
    [MenuItem("Tools/WinterFramwork/Create XMLScriptData")]
    static void CreateXMLScriptData()
    {

        if (!Directory.Exists(XMLConfig.configPath)) Directory.CreateDirectory(XMLConfig.configPath);
        if (!Directory.Exists(XMLConfig.dataPath)) Directory.CreateDirectory(XMLConfig.dataPath);
        if (!Directory.Exists(XMLConfig.codePath)) Directory.CreateDirectory(XMLConfig.codePath);

        sheetDatas = new Dictionary<string, SheetData>();
        typeSheetDatas = new Dictionary<string, TypeSheetData>();

        string[] files = System.IO.Directory.GetFiles(XMLConfig.configPath, "*.xml");
        foreach (string file in files)
        {
            Debug.Log(file);
            if (!Path.GetFileNameWithoutExtension(file).StartsWith("#"))
            {
                SheetData sd = ReadSheetDataFile(file);
                sheetDatas.Add(sd.SheetName, sd);
            }
            else
            {
                switch (Path.GetFileNameWithoutExtension(file))
                {
                    case "#Enum":
                        enumSheetDatas = ReadEnumFile(file);
                        break;
                    case "#Type":
                        typeSheetDatas = ReadTypeSheetDataFile(file);
                        break;
                }


            }

        }

        byte[] buff = BinarySerializationHelper.SerializeToBinary(sheetDatas);
        sheetDatas = BinarySerializationHelper.DeserializeFromBinary<Dictionary<string, SheetData>>(buff);
        File.WriteAllBytes(Path.Combine(XMLConfig.dataPath, "TBData"), buff);

        if (enumSheetDatas != null) ScriptConvert.GenerateEnumFile(enumSheetDatas);


        foreach (var item in sheetDatas)
        {
            ScriptConvert.GenerateClassFile(item.Value);
        }

        foreach (var item in typeSheetDatas)
        {
            ScriptConvert.GenerateClassFile(item.Value);
        }

        ScriptConvert.GenerateManagerFile(sheetDatas);

        AssetDatabase.Refresh();

        //AssetDatabase.ImportAsset("Assets/YourGeneratedAsset.cs", ImportAssetOptions.ForceUpdate);
    }

    /// <summary>
    /// 读取指定路径的 XML 文件，并将其内容解析为 SheetData 对象。
    /// </summary>
    /// <param name="filePath">XML 文件的路径。</param>
    /// <returns>包含解析数据的 SheetData 对象。如果文件不存在，则返回 null。</returns>
    
    static SheetData ReadSheetDataFile(string filePath)
    {
        // 检查文件是否存在
        if (!File.Exists(filePath))
        {
            Debug.LogError("XML文件不存在: " + filePath);
            return null;
        }

        // 加载 XML 文件到 XmlDocument 对象
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(filePath);

        // 获取 XML 文件的根节点
        XmlElement rootElement = xmlDoc.DocumentElement;

        // 创建命名空间管理器，用于处理 XML 中的命名空间
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);

        // 如果根节点包含默认命名空间，则将其添加到命名空间管理器
        if (rootElement.HasAttribute("xmlns"))
        {
            string defaultNamespace = rootElement.GetAttribute("xmlns");
            nsmgr.AddNamespace("ss", defaultNamespace); // 使用前缀 "ss" 表示默认命名空间
        }

        // 获取 XML 文件中所有的行节点（Row）
        XmlNodeList rows = xmlDoc.SelectNodes("//ss:Row", nsmgr);

        // 创建一个 SheetData 对象，用于存储解析后的数据
        SheetData xsd = new SheetData();
        xsd.datas = new List<List<string>>(); // 初始化数据列表
        Debug.Log(filePath);

        // 遍历每一行节点
        for (int i = 0; i < rows.Count; i++)
        {
            // 获取当前行中的所有单元格（Cell）和单元格数据（Data）
            XmlNodeList cells = rows[i].SelectNodes("ss:Cell", nsmgr);
            XmlNodeList datas = rows[i].SelectNodes("ss:Cell/ss:Data", nsmgr);

            // 为当前行创建一个新的列表，用于存储单元格数据
            xsd.datas.Add(new List<string>());

            // 遍历当前行的每个单元格
            foreach (XmlNode cell in cells)
            {
                // 检查单元格是否有 "ss:Index" 属性（表示单元格的索引位置）
                if (cell.Attributes.GetNamedItem("ss:Index") == null)
                {
                    // 如果没有索引属性，则直接读取单元格中的数据
                    if (cell.SelectSingleNode("ss:Data", nsmgr) != null)
                    {
                        xsd.datas[xsd.datas.Count - 1].Add(cell.SelectSingleNode("ss:Data", nsmgr).InnerText);
                    }
                    else
                    {
                        // 如果单元格没有数据，则添加一个空字符串
                        xsd.datas[xsd.datas.Count - 1].Add("");
                    }
                }
                else
                {
                    // 如果有索引属性，则根据索引填充空单元格
                    while (int.Parse(cell.Attributes["ss:Index"].Value) != xsd.datas[xsd.datas.Count - 1].Count)
                    {
                        xsd.datas[xsd.datas.Count - 1].Add("");
                    }

                    // 填充当前单元格的数据
                    if (cell.SelectSingleNode("ss:Data", nsmgr) != null)
                    {
                        xsd.datas[xsd.datas.Count - 1][xsd.datas[xsd.datas.Count - 1].Count - 1] = (cell.SelectSingleNode("ss:Data", nsmgr).InnerText);
                    }
                }
            }

            // 从第 4 行开始，检查数据的列数是否与表头的列数一致
            if (i > 3)
            {
                if (xsd.datas[i].Count != xsd.Names.Count && !XMLConfig.CheckStartsWith(xsd.datas[i][0], XMLConfig.jumpTag))
                {
                    Debug.LogError($"Table load error:{Path.GetFileNameWithoutExtension(filePath)}\nLine:{i + 1},Data id:{xsd.datas[i][0]},cell count error {xsd.datas[i].Count}/{xsd.Names.Count}");
                }
            }
        }

        //设置 SheetData 的名称为文件名（不包含扩展名）
        xsd.SetName(Path.GetFileNameWithoutExtension(filePath));

        #region 获取Worksheet节点 获取SheetName属性
        //XmlNode worksheetNode = xmlDoc.SelectSingleNode("//ss:Worksheet", nsmgr);
        //if (worksheetNode != null && worksheetNode.Attributes["ss:Name"] != null)
        //{
        //    worksheetNode.Attributes["ss:Name"].Value;
        //}
        #endregion



        return xsd;

    }

    static Dictionary<string, TypeSheetData> ReadTypeSheetDataFile(string filePath)
    {
        // 检查文件是否存在
        if (!File.Exists(filePath))
        {
            Debug.LogError("XML文件不存在: " + filePath);
            return null;
        }
        // 加载XML文件
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(filePath);

        // 获取根节点的命名空间
        XmlElement rootElement = xmlDoc.DocumentElement;

        XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);

        if (rootElement.HasAttribute("xmlns"))
        {
            string defaultNamespace = rootElement.GetAttribute("xmlns");
            nsmgr.AddNamespace("ss", defaultNamespace);
        }

        // 获取所有行
        XmlNodeList rows = xmlDoc.SelectNodes("//ss:Row", nsmgr);
        Dictionary<string, TypeSheetData> dic = new Dictionary<string, TypeSheetData>();

        for (int i = 0; i < rows.Count; i++)
        {
            XmlNodeList nodeData = rows[i].SelectNodes("ss:Cell/ss:Data", nsmgr);


            if (nodeData.Count > 0 && nodeData[0].InnerText.StartsWith('#'))
            {
                Debug.Log(nodeData[0].InnerText);
                dic.Add(nodeData[0].InnerText.Trim('#'), new TypeSheetData(rows, i, nsmgr));
                i += 3;
            }

        }

        return dic;

    }
    static Dictionary<string, EnumSheetData> ReadEnumFile(string filePath)
    {
        // 检查文件是否存在
        if (!File.Exists(filePath))
        {
            Debug.LogError("XML文件不存在: " + filePath);
            return null;
        }
        // 加载XML文件
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.Load(filePath);

        // 获取根节点的命名空间
        XmlElement rootElement = xmlDoc.DocumentElement;

        XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);

        if (rootElement.HasAttribute("xmlns"))
        {
            string defaultNamespace = rootElement.GetAttribute("xmlns");
            nsmgr.AddNamespace("ss", defaultNamespace);
        }

        // 获取所有行
        XmlNodeList rows = xmlDoc.SelectNodes("//ss:Row", nsmgr);
        Dictionary<string, EnumSheetData> dic = new Dictionary<string, EnumSheetData>();

        for (int i = 0; i < rows.Count; i++)
        {
            XmlNodeList nodeData = rows[i].SelectNodes("ss:Cell/ss:Data", nsmgr);
            if (nodeData.Count > 0 && nodeData[0].InnerText.StartsWith("#"))
            {

                Debug.Log(nodeData[0].InnerText);
                dic.Add(nodeData[0].InnerText.Trim('#'), new EnumSheetData(rows, i, nsmgr));
                i += 2;
            }

        }
        return dic;
    }
}

public static class ScriptConvert
{
    
    static int ab;
    //[ContextMenu("Generate File")]
    public static void GenerateEnumFile(Dictionary<string, EnumSheetData> datas)
    {
        StringBuilder stringBuilder = new StringBuilder();

        foreach (var item in datas)
        {
            AppendEnum(stringBuilder, item.Value);
        }
        // 写入文件内容
        SaveFile(XMLConfig.codePath, "TableEnum.cs", stringBuilder.ToString());
        stringBuilder.Clear();
    }
    public static void AppendEnum(StringBuilder stringBuilder, EnumSheetData enumData)
    {
        AppendSummary(enumData.EnumSummary, stringBuilder,"");
        stringBuilder.Append($"public enum {enumData.EnumName} {{\n");
        for (int i = 0; i < enumData.value.Count; i++)
        {
            if (string.IsNullOrEmpty(enumData.intValue[i]))
                stringBuilder.Append($"\t\t{enumData.value[i]}, //{enumData.valusSummarys[i].Replace('\n',' ')}\n");
            else
                stringBuilder.Append($"\t\t{enumData.value[i]} = {enumData.intValue[i]}, //{enumData.valusSummarys[i].Replace('\n', ' ')}\n");
        }
        stringBuilder.Append($"}}\n\n");
    }
    public static void GenerateClassFile(ISheetData data)
    {
        StringBuilder stringBuilder = new StringBuilder();

        AppendClassHead(stringBuilder, data.SheetName, true);

        for (int i = 0; i < data.Commits.Count; i++)
        {
            if (!XMLConfig.CheckStartsWith(data.Commits[i], XMLConfig.jumpTag) && (i >= data.Groups.Count || XMLConfig.CheckGroup(data.Groups[i])))
            {
                AppendSummary(data.Commits[i], stringBuilder, "\t");
                stringBuilder.Append(CheckAndConverterAtrribute(data.Types[i], data.Names[i])+"\n");
                stringBuilder.Append($"\tpublic readonly {CheckAndConverterType(data.Types[i])} {data.Names[i]};\n");
            }
        }

        stringBuilder.Append($"\tpublic {data.SheetName}(List<string> datas)\n");
        stringBuilder.Append("{\t\n");
        stringBuilder.Append("\t\tint value = 0;\n");
        for (int i = 0; i < data.Commits.Count; i++)
        {
            if (!XMLConfig.CheckStartsWith(data.Commits[i], XMLConfig.jumpTag) && (i >= data.Groups.Count || XMLConfig.CheckGroup(data.Groups[i])))
            {
                TypeConvert(stringBuilder, data.Types[i], data.Names[i], $"datas[{i}]");
                stringBuilder.Append("\n");
            }
        }
        stringBuilder.Append("\t}\n");

        AppendClassEnd(stringBuilder);
        // 写入文件内容
        SaveFile(XMLConfig.codePath, data.SheetName + ".cs", stringBuilder.ToString());

        stringBuilder.Clear();
        //Debug.Log("File generated successfully: " + filePath);
    }
    static void AppendSummary(string content, StringBuilder stringBuilder, string start)
    {
        stringBuilder.Append($"{start}/// <summary>\n");
        string[] temps = content.Split('\n', '\r');
        for (global::System.Int32 j = 0; j < temps.Length; j++)
        {
            stringBuilder.Append($"{start}///{temps[j]}\n");
        }
        stringBuilder.Append($"{start}/// <summary>\n");
    }
    public static string CheckAndConverterAtrribute(string type,string name)
    {
        string atrribute = "";
        if (type.StartsWith("PropertyFloat"))
        {
            atrribute = $"[PropAttribute(PropertyEnum.{name})]";
        }
        return atrribute;
    }

    public static string CheckAndConverterType(string type)
    {
        if (type.StartsWith("map"))
        {
            type = "Dictionary" + type.Substring(3, type.Length - 3);
        }
        if (type.StartsWith("type"))
        {
            type = "List"+ type.Substring(4, type.Length - 4);
        }
        if (type.StartsWith("enum"))
        {
            type = type.Substring(4, type.Length - 4).Trim('<', '>');
        }
        if (type.StartsWith("PropertyFloat"))
        {
            type =  "float";
        }
        return type;
    }
    public static void GenerateManagerFile(Dictionary<string, SheetData> datas)
    {
        StringBuilder stringBuilder = new StringBuilder();

        AppendClassHead(stringBuilder, "TableDataMgr:SingleMono<TableDataMgr>", false);

        AppendDataTb(stringBuilder, datas);

        stringBuilder.Append("\n");

        AppendInit(stringBuilder, datas);

        AppendDicconvert(stringBuilder);

        AppendListconvert(stringBuilder);

        AppendClassEnd(stringBuilder);

        SaveFile(XMLConfig.codePath, "TableDataMgr.cs", stringBuilder.ToString());

    }
    public static void AppendDataTb(StringBuilder stringBuilder, Dictionary<string, SheetData> datas)
    {
        foreach (var item in datas)
        {
            stringBuilder.Append($"\tpublic Dictionary<string,{item.Value.SheetName}> {item.Value.SheetName}TB => {item.Value.SheetName}tb;\n ");
            stringBuilder.Append($"\tDictionary<string,{item.Value.SheetName}> {item.Value.SheetName}tb;\n ");
        }

    }
    public static void AppendDicconvert(StringBuilder stringBuilder)
    {
        stringBuilder.Append("    public Dictionary<T, U> ConvertMap<T, U>(string str, char kvspl, char arryspl, Func<string, T> convertKey, Func<string, U> convertValue)\r\n    {\r\n\t\tif (str == null || str.Length == 0 || str == \"-\" || str == \"0\") return null;\r\n        Dictionary<T, U> dic = new Dictionary<T, U>();\r\n        string[] items = str.Split(arryspl);\r\n        string[] kvs;\r\n        for (int i = 0; i < items.Length; i++)\r\n        {\r\n            kvs = items[i].Split(kvspl);\r\n            dic.Add(convertKey(kvs[0]), convertValue(kvs[1]));\r\n        }\r\n        return dic;\r\n    }\n");
    }

    public static void AppendListconvert(StringBuilder stringBuilder)
    {
        stringBuilder.Append("    public List<T> ConvertTypeList<T>(string data,char itemSpl,char fieldSpl, Func<List<string>,T> converter) \r\n    { \r\n\t\tif (string.IsNullOrEmpty(data)  || data.Length == 0 || data == \"-\" || data == \"0\") return null;\r\n        List<T> list = new List<T>();\r\n        data = data.Trim('\\n');\r\n        string[] items = data.Split(itemSpl);\r\n        for (int i = 0; i < items.Length; i++) {\r\n            string[] fields = items[i].TrimEnd('\\n').Split(fieldSpl);\r\n            list.Add( converter.Invoke(new List<string>(fields)));\r\n        }\r\n        return list;\r\n    }\n");
    }


    public static void AppendInit(StringBuilder stringBuilder, Dictionary<string, SheetData> datas)
    {
        stringBuilder.Append("\tpublic void Init()\n");
        stringBuilder.Append("\t{\n");

        stringBuilder.Append("\t\tDictionary<string, SheetData> sheetDatas = BinarySerializationHelper.DeserializeFromBinary <Dictionary<string, SheetData>> (File.ReadAllBytes(Path.Combine(XMLConfig.dataPath, \"TBData\")));\n");

        foreach (var item in datas)
        {
            stringBuilder.Append($"\t\t{item.Value.SheetName}tb = new Dictionary<string,{item.Value.SheetName}>();\n");
            stringBuilder.Append($"\t\tfor (int i = 4; i < sheetDatas[\"{item.Value.SheetName}\"].datas.Count; i++)\n\t\t{{\n");
            stringBuilder.Append($"\t\t\tif (!XMLConfig.CheckStartsWith(sheetDatas[\"{item.Value.SheetName}\"].datas[i][0], XMLConfig.jumpTag))\n");
            stringBuilder.Append($"\t\t\t{item.Value.SheetName}tb.Add(sheetDatas[\"{item.Value.SheetName}\"].datas[i][0],new {item.Value.SheetName}(sheetDatas[\"{item.Value.SheetName}\"].datas[i])) ;\n");
            stringBuilder.Append($"\t\t}}\n");
        }

        stringBuilder.Append("\t}\n");
        stringBuilder.Append("\n");
    }
    public static void AppendClassHead(StringBuilder stringBuilder, string className, bool isSerialize)
    {
        stringBuilder.Append("using UnityEngine;\n");
        stringBuilder.Append("using System;\n");
        stringBuilder.Append("using System.IO;\n");
        stringBuilder.Append("using System.Collections.Generic;\n");
        stringBuilder.Append("using WFTools;\n");
        stringBuilder.Append("\n");

        if (isSerialize)
        {
            stringBuilder.Append("[Serializable]\n");
        }
        stringBuilder.Append($"public class {className} \n");
        stringBuilder.Append("{ \n");
    }
    public static void AppendClassEnd(StringBuilder stringBuilder)
    {
        stringBuilder.Append("} \n");
    }
    public static void SaveFile(string path, string fileName, string content)
    {
        string filePath = Path.Combine(path, fileName);
        File.WriteAllText(filePath, content);
    }
    public static void SaveFile(string path, string fileName, byte[] content)
    {
        string filePath = Path.Combine(path, fileName);
        File.WriteAllBytes(filePath, content);
    }
    static string MapTypeConvert(string type)
    {
        switch (type)
        {

            case "bool":
                return "s => (int.Parse(s) == 1).ToString()";
            case "float":
                return "float.Parse";
            case "int":
                return "int.Parse";
            case "string":
                return "s => s";
            default:
                return "s => s";
        }
    }
    static string TypeConvert(StringBuilder stringBuilder, string type, string name, string data)
    {
        switch (type)
        {
            case "bool":
                stringBuilder.Append("\t\t" + name + " = int.Parse(" + data + ")==1;");
                break;
            case "float":
                stringBuilder.Append("\t\t" + name + " = float.Parse(" + data + ");");
                break;
            case "float[]":
                stringBuilder.Append(string.Format("\t\t{0} = Array.ConvertAll({1}.Split('{2}'), float.Parse);", (object)name, (object)data, XMLConfig.arraySplitTag));
                break;
            case "int":
                stringBuilder.Append("\t\t" + name + " = int.Parse(" + data + ");");
                break;
            case "int[]":
                stringBuilder.Append(string.Format("\t\t{0} = Array.ConvertAll({1}.Split('{2}'), int.Parse);", (object)name, (object)data, XMLConfig.arraySplitTag));
                break;
            case "string":
                stringBuilder.Append("\t\t" + name + " = " + data + ";");
                break;
            case "string[]":
                stringBuilder.Append(string.Format("\t\t{0} = {1}.Split('{2}');", (object)name, (object)data, XMLConfig.arraySplitTag));
                break;
            case "PropertyFloat":
                stringBuilder.Append("\t\t" + name + " = float.Parse(" + data + ");");
                break;
            default:
                if (type.StartsWith("map"))
                {
                    string typeSub = type.Substring(3, type.Length - 3).Trim('<', '>');
                    string keyType = typeSub.Split(',')[0];
                    string valueType = typeSub.Split(',')[1];

                    stringBuilder.Append($"\t\t{name} = TableDataMgr.Instance.ConvertMap<{typeSub}>({data},'{XMLConfig.mapSplitTag}','{XMLConfig.arraySplitTag}',{MapTypeConvert(keyType)}, {MapTypeConvert(valueType)});");
                }
                else if (type.StartsWith("type"))
                {
                    string typeSub = type.Substring(4, type.Length - 4).Trim('<', '>');
                    stringBuilder.Append($"\t\t{name} = TableDataMgr.Instance.ConvertTypeList<{typeSub}>({data},'{XMLConfig.typeItemSplitTag.ToString().Replace("\n", "\\n")}','{XMLConfig.typeFieldSplitTag}',s => new {typeSub}(s));");
                }
                else if (type.StartsWith("enum")){
                    string typeSub = type.Substring(4, type.Length - 4).Trim('<', '>');
                    stringBuilder.Append($"\t\tif (int.TryParse({data},out value))");
                    stringBuilder.Append($"{name} = ({typeSub})int.Parse({data});");
                    stringBuilder.Append($" else {name} = ({typeSub})Enum.Parse(typeof({typeSub}),{data});");
                }
                else
                {
                    stringBuilder.Append($"\t\tif (int.TryParse({data},out value))");
                    stringBuilder.Append($"{name} = ({type})int.Parse({data});");
                    stringBuilder.Append($" else {name} = ({type})Enum.Parse(typeof({type}),{data});");
                }


                break;
        }
        return stringBuilder.ToString();
    }
}